﻿using System;
using System.Collections.Generic;
using System.Web;
using System.IO.Compression;
using System.Text.RegularExpressions;
using System.Web.Caching;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace Fly.Net
{

    public class ResourceLoader : IHttpHandler
    {
        #region
        string _urlFormat;
        public virtual string UrlFormat
        {
            get { return _urlFormat; }
            set { _urlFormat = value == "" ? null : value; }
        }

        public virtual string[] Files { get; set; }

        public virtual List<ResourceInfo> Resources { get; set; }

        public string FileStr { get; set; }

        public ResourceTypes ResourceType { get; set; }

        public virtual string FileLoadFailMessage
        {
            get { return "/*File fail to load:{0}\n{1}*/"; }
        }

        public virtual string Content
        {
            get;
            set;
        }

        public virtual string CacheKey
        {
            get
            {
                return HttpContext.Current.Request.RawUrl;
            }
        }

        CompressTypes CompressType = CompressTypes.None;

        public virtual string ContentKey { get; set; }

        public virtual string ETag
        {
            get
            {
                return ContentKey + "-" + ((int)CompressType);
            }
        }

        public const string KeyConentSeparator = "=";

        #endregion

        #region IHttpHandler 成员

        public virtual bool IsReusable
        {
            get { return false; }
        }

        public virtual void ProcessRequest(HttpContext context)
        {
            var req = HttpContext.Current.Request.QueryString;
            this.UrlFormat = req["format"];


            FileStr = req["css"];
            if (!string.IsNullOrEmpty(FileStr))
            {
                this.ResourceType = ResourceTypes.CSS;
                HttpContext.Current.Response.ContentType = "text/css";
            }
            else
            {
                FileStr = req["js"];
                this.ResourceType = ResourceTypes.JS;
            }

            if (!string.IsNullOrEmpty(FileStr))
            {
                this.SetHeader();
                this.Files = FileStr.Split('|');
                this.ReadFiles();
                this.OutputContent();
                HttpContext.Current.Response.Flush();
                HttpContext.Current.Response.End();
            }
        }

        #endregion

        #region 开启 Gzip 或 Deflate 压缩
        /// <summary>
        /// 开启 Gzip 或 Deflate 压缩
        /// </summary>
        public virtual void EnableCompress()
        {
            if (HttpContext.Current.Request.QueryString["comp"] == "1")
            {
                HttpUtils.EnableCompress();
            }
        }
        #endregion

        #region SetHeader
        public virtual void SetHeader()
        {
            if (this.ResourceType == ResourceTypes.CSS)
            {
                HttpContext.Current.Response.ContentType = "text/css";
            }
            else if (this.ResourceType == ResourceTypes.JS)
            {
                HttpContext.Current.Response.ContentType = "text/javascript";
            }

            EnableCompress();
        }
        #endregion

        public virtual string GetResourceAbsoluteUrl(string url)
        {
            if (url.StartsWith("@"))
            {
                url = url.TrimStart('@');
            }
            else if (this.UrlFormat != null)
            {
                url = string.Format(this.UrlFormat, url);
            }
#if DEBUG
            url = url.Replace("-min", "");
#endif
            return HttpUtils.ToAbsolute(url);
        }

        public virtual string GetFileContent(string file)
        {
            try
            {
                var sr = new System.IO.StreamReader(file);
                return sr.ReadToEnd();
            }
            catch (Exception ex)
            {
                return string.Format(this.FileLoadFailMessage, file, ex.Message);
            }
        }

        Regex _backgroundUrl = new Regex(@"\b*url\s*\([\s'""]*(?<url>[^/][^)'""]+)[\s'""]*\)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);
        public virtual Regex BackgroundUrl
        {
            get
            {
                return _backgroundUrl;
            }
        }

        public virtual string ProcessCSSRelativeUrl(string url, string path, string str)
        {
            var urlDir = Regex.Replace(url, "[^/]*$", "");
            if (urlDir != "")
            {
                str = BackgroundUrl.Replace(str, o =>
                {
                    var u = o.Groups["url"].Value;
                    if (u.StartsWith("/"))
                        return o.Value;
                    return " url(" + urlDir + u + ")";
                });
            }
            return str;
        }

        public virtual string ProcessFileContent(string url, string path, string str)
        {
            if (this.ResourceType == ResourceTypes.CSS)
            {
                str = this.ProcessCSSRelativeUrl(url, path, str);
            }

            return str;
        }

        public virtual string GetFileKey(string path)
        {
            var info = new FileInfo(path);
            return info.LastWriteTime.Ticks + "-" + info.Length;
        }

        protected bool IsCachedOnBrowser()
        {
            var clientEtag = HttpContext.Current.Request.ServerVariables["HTTP_IF_NONE_MATCH"];
            if (!string.IsNullOrEmpty(clientEtag) && clientEtag.Equals(ETag))
            {
                HttpContext.Current.Response.ClearHeaders();
                HttpContext.Current.Response.AppendHeader("Etag", ETag);
                HttpContext.Current.Response.AppendHeader("Content-Length", "0");
                HttpContext.Current.Response.Status = "304 Not Modified";
                return true;
            }
            return false;
        }

        public virtual void ReadFiles()
        {
            var res = HttpContext.Current.Response;
            var cacheKey = HttpContext.Current.Request.RawUrl;
            var cache = HttpContext.Current.Cache.Get(cacheKey);

            if (cache == null)
            {

                var buffer = new StringBuilder();
                this.Resources = new List<ResourceInfo>();
                var files = new string[this.Files.Length];
                var keys = new string[this.Files.Length];

                for (var i = 0; i < this.Files.Length; i++)
                {
                    var url = this.GetResourceAbsoluteUrl(this.Files[i]);
                    var re = new ResourceInfo
                    {
                        Url = url,
                        Path = HttpContext.Current.Server.MapPath(url),
                    };
                    re.Content = this.ProcessFileContent(url, re.Path, this.GetFileContent(re.Path));
                    buffer.Append(re.Content);
                    buffer.Append(this.ResourceType == ResourceTypes.CSS ? "\n" : ";\n");
                    files[i] = re.Path;
                    keys[i] = GetFileKey(re.Path);
                }

                CacheDependency fileDependency = new CacheDependency(files);

                Content = buffer.ToString();

                ContentKey = Security.GetMd5Sum(string.Join(",", keys));

                HttpContext.Current.Cache.Insert(cacheKey, ContentKey + KeyConentSeparator + Content, fileDependency);
            }
            else
            {
                var str = (string)cache;
                var i = str.IndexOf(KeyConentSeparator);
                ContentKey = str.Substring(0, i);
                Content = str.Substring(i + 1);
            }
        }

        public virtual void OutputContent()
        {
            if (!this.IsCachedOnBrowser())
            {
                HttpContext.Current.Response.Write(Content);
                HttpContext.Current.Response.AppendHeader("Etag", ETag);
            }
        }
    }
}